package com.quanxi.qxaimain.common.util.ExcelResultHandler;

import com.quanxi.qxaimain.common.util.ExcelResultHandler.MethodAccessor;
import com.quanxi.qxaimain.common.util.ExcelResultHandler.Reflector;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @Author lyx
 * @create 2022/11/30 16:02
 */
@Slf4j
public   abstract class   ExcelExportResultHandler<T> implements ResultHandler<T> {

    private SXSSFWorkbook workBook;//工作薄

    private Sheet sheet=null;//工作表

    private String excelName="默认";//excel名称

    private String[] headerNames=null;//表头

    private AtomicInteger currentRowNumber = new AtomicInteger(0);//记录当前excel行号，从0开始

    private  int excelSize=1000000; // 每页excel最大行数 默认1百万

    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private boolean isExportZip = true;     //定义导出成zip格式的还是原始的xlsx格式

    public ExcelExportResultHandler (String excelName ){
        this.workBook=new  SXSSFWorkbook();
        this.excelName=excelName;

    }

//    public ExcelExportResultHandler (String excelName, String[] headerNames  ){
//        this(excelName);
//        this.headerNames=headerNames;
//
//    }

    public ExcelExportResultHandler (String excelName,  boolean isExportZip){
       this(excelName);
       this.isExportZip=isExportZip;
    }

    public void setExcelSize(int excelSize) {
        this.excelSize = excelSize;
    }
    public void setHeaderNames(String[] headerNames) {
        this.headerNames = headerNames;
    }




    //抽象方法，提供给子类进行实现，遍历写入数据到excel
    public abstract void tryFetchDataAndWriteToExcel();



    public void handleResult(ResultContext<? extends T> resultContext) {
        //获取数据，并回调ExportExcelUtils中的方法进行数据写入到excel，固定写法即可，不需要修改
        Object aRowData = resultContext.getResultObject();
        //获取表头字段，只获取一次
        if (this.headerNames==null){
            Set<String> set =  ((Map)aRowData).keySet();
            List<String> list = new ArrayList(set);
            String[] headname = new String[list.size()];
            list.toArray(headname ); //获取Excel列名
            setHeaderNames(headname);
        }
        callBackWriteRowdataToExcel(aRowData);
    }

    /**
     * 将数据写出到excel中
     *
     *
     */
    public void exportExcel()   {
        log.info("--------->>>>写入Excel开始.." );

        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();

        response.setContentType("application/octet-stream");

        ZipOutputStream zos = null;

        OutputStream os = null;

        try {

            //如果设置成了导出成Zip，格式加上三行以下代码进行Zip的处理
            if (isExportZip) {

                response.setHeader("Content-Disposition", "attachment;filename=" + new String((excelName+".zip").replaceAll(" ", "").getBytes("utf-8"),"iso8859-1"));

                os = new BufferedOutputStream(response.getOutputStream());

                zos = new ZipOutputStream(os);

                ZipEntry zipEntry = new ZipEntry(new String((excelName + ".xlsx").replaceAll(" ", "")));

                zos.putNextEntry(zipEntry);
            } else {

                response.setHeader("Content-Disposition", "attachment;filename=" + new String((excelName+".xlsx").replaceAll(" ", "").getBytes("utf-8"),"iso8859-1"));

                os = new BufferedOutputStream(response.getOutputStream());
            }

            workBook.setCompressTempFiles(false);

            //写入数据
            /****************************/
            //调用具体的实现子类的代码，尝试获取数据进行遍历并写入excel
            tryFetchDataAndWriteToExcel();

            //最后打印一下最终写入的行数
            log.info("--------->>>> write to excel size now is {}", currentRowNumber.get());

            log.info("--------->>>>   导出之前先自动设置列宽");

            autoAllSizeColumn();

            log.info("--------->>>>   正在导出。。。");

            if (isExportZip) {
                workBook.write(zos);
            } else {
                workBook.write(os);
            }

            if (workBook != null) {
                workBook.dispose();// 删除临时文件，很重要，否则磁盘可能会被写满
            }

            workBook.close();

            /****************************/
            log.info("--------->>>>全部数据写入Excel完成..");

        }catch (Exception e){

            log.error("error",e);
        }
        finally {
            //关闭资源
            if(isExportZip){
                try {if(zos!=null) zos.close();} catch (IOException e1) {log.error("error",e1);	}
            }else{
                try {if(os!=null) os.close();} catch (IOException e1) {log.error("error",e1);	}
            }
        }

    }


    /**
     * 将数据写出到excel中

     * @param path 导出文件路径
     */
    public void exportExcel(String path)  {
        log.info("--------->>>>写入Excel开始.." );

        OutputStream os = null;

        ZipOutputStream zos = null;
        try {
            //如果设置成了导出成Zip，格式加上三行以下代码进行Zip的处理
            if (isExportZip) {

                os = new FileOutputStream(new File(path + "\\" + excelName + ".zip"));

                zos = new ZipOutputStream(os);

                ZipEntry zipEntry = new ZipEntry(new String((excelName + ".xlsx").replaceAll(" ", "")));

                zos.putNextEntry(zipEntry);
            }
            else {
                os = new FileOutputStream(new File(path + "\\" + excelName + ".xlsx"));
            }

            workBook.setCompressTempFiles(false);

            //写入数据
            /****************************/
            //调用具体的实现子类的代码，尝试获取数据进行遍历并写入excel
            tryFetchDataAndWriteToExcel();

            //最后打印一下最终写入的行数
            log.info("--------->>>> write to excel size now is {}", currentRowNumber.get());

            log.info("--------->>>>   导出之前先自动设置列宽");

            autoAllSizeColumn();

            log.info("--------->>>>   正在导出。。。");

            if (isExportZip) {
                workBook.write(zos);
            } else {
                workBook.write(os);
            }

            if (workBook != null) {
                workBook.dispose();// 删除临时文件，很重要，否则磁盘可能会被写满
            }

            workBook.close();

            /****************************/

            log.info("--------->>>>全部数据写入Excel完成..");
        }catch (Exception e){

            log.error("error",e);
        }
        finally {
            //关闭资源
            if(isExportZip){
                try {if(zos!=null) zos.close();} catch (IOException e1) {log.error("error",e1);	}
            }else{
                try {if(os!=null) os.close();} catch (IOException e1) {log.error("error",e1);	}
            }
        }


    }



    //写入一行数据到excel中,提供给ResultHandler中遍历时进行回调调用
    public void callBackWriteRowdataToExcel(Object aRowData){

        if(currentRowNumber.get()%excelSize==0){
            int L=currentRowNumber.get()/excelSize+1;
            sheet = workBook.createSheet("页数("+L+")");
            sheet = workBook.getSheetAt(currentRowNumber.get()/excelSize);		//动态指定当前的工作表 每个sheet存放1000000行数据
            initHeader();
         //   autoAllSizeColumn();

//            Row row = sheet.createRow(0);
//            for (int cellNumber = 0; cellNumber < headerNames.length; cellNumber++) {
//                Cell cell = row.createCell(cellNumber);
//                cell.setCellValue(headerNames[cellNumber]); //写入表头数据
//            }
        }


        //反射获取值并设置到excel的中cell列中
        MethodAccessor methodAccessor = Reflector.getMethodAccessor(aRowData.getClass());

        Row row = sheet.createRow(currentRowNumber.get()%excelSize+1);//创建下一行

        for (int cellNumber = 0; cellNumber < headerNames.length; cellNumber++){

            //aRowData为map时，要特殊处理进行获取。不能通过methodAccessor反射调用.
            Object value = null;
            if(aRowData instanceof Map){
                value = ((Map)aRowData).get(headerNames[cellNumber]);
            }else {
                value = methodAccessor.getFieldValue(aRowData, headerNames[cellNumber]);
            }
            Cell cell = row.createCell(cellNumber);

            //date类型默认转换string格式化日期
            if (value!=null && value instanceof Date){
                cell.setCellValue(sdf.format(value));//
            }else {
                cell.setCellValue(value==null?"":value.toString());//写入数据
            }
        }
            //先将行号增加
            currentRowNumber.incrementAndGet();
            //每写入100000条就打印一下
            if(currentRowNumber.get() % 100000 == 0 ){
                log.info("--------->>>> write to excel size now is {}", currentRowNumber.get() );
            }

    }





    /**
     * 初始化表头信息
     */
    private void initHeader()  {

        // 创建第一行
        Row row = sheet.createRow(0);
        Cell cell = null;
        // 创建表头
        for (int i = 0; i < headerNames.length; i++) {
            cell = row.createCell(i);
            cell.setCellValue(headerNames[i]);
            setHeaderCellStyle(cell);
        }
    }

    /**
     * 设置表头单元格样式
     *
     * @param cell 单元格
     */
    public void setHeaderCellStyle(Cell cell) {
        // 设置样式
        CellStyle cellStyle  = workBook.createCellStyle();
        // 设置字体居中
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        // 设置字体
        Font font = workBook.createFont();
        font.setFontName(HSSFFont.FONT_ARIAL);
        // 设置加粗
         font.setBold(true);
        // 设置字号
        font.setFontHeightInPoints((short) 13);
        cellStyle.setFont(font);
        cell.setCellStyle(cellStyle);
    }

    /**
     * 根据表头自动调整列宽度
     */
    public void autoAllSizeColumn() {
        // 如果是SXSSFSheet，需要调用trackAllColumnsForAutoSizing方法一次
        if (sheet instanceof SXSSFSheet) {
            SXSSFSheet tmpSheet = (SXSSFSheet) sheet;
            tmpSheet.trackAllColumnsForAutoSizing();
        }
        for (int i = 0, length = headerNames.length; i < length; i++) {
          //  sheet.autoSizeColumn(i);//根据表头自动列宽
                sheet.setColumnWidth(i,sheet.getColumnWidth(i)*18/10);// 根据内容自动列宽
        }
    }




}